package com.atguigu.srb.core.service.impl;

import com.alibaba.fastjson.JSON;
import com.atguigu.srb.base.dto.SmsDTO;
import com.atguigu.srb.base.util.Asserts;
import com.atguigu.srb.base.util.JwtUtils;
import com.atguigu.srb.common.result.ResponseEnum;
import com.atguigu.srb.core.entity.TransFlow;
import com.atguigu.srb.core.entity.UserAccount;
import com.atguigu.srb.core.entity.UserInfo;
import com.atguigu.srb.core.enums.LendStatusEnum;
import com.atguigu.srb.core.enums.TransTypeEnum;
import com.atguigu.srb.core.hfb.FormHelper;
import com.atguigu.srb.core.hfb.HfbConst;
import com.atguigu.srb.core.hfb.RequestHelper;
import com.atguigu.srb.core.mapper.UserAccountMapper;
import com.atguigu.srb.core.service.TransFlowService;
import com.atguigu.srb.core.service.UserAccountService;
import com.atguigu.srb.core.service.UserInfoService;
import com.atguigu.srb.core.util.*;
import com.atguigu.srb.rabbitutil.constant.MQConst;
import com.atguigu.srb.rabbitutil.service.MQService;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * <p>
 * 用户账户 服务实现类
 * </p>
 *
 * @author Atguigu
 * @since 2023-06-30
 */
@Service
@Slf4j
public class UserAccountServiceImpl extends ServiceImpl<UserAccountMapper, UserAccount> implements UserAccountService {
    @Resource
    private UserInfoService userInfoService;
    @Resource
    TransFlowService transFlowService;
    @Resource
    RabbitTemplate rabbitTemplate;
    @Resource
    MQService mqService;


    /**
     * 处理充值业务的同步接口
     * @param amount 用户充值的金额
     * @param token 用户的token
     * @return form表单,里面配置好了hfb对应接口需要的一系列参数
     */
    @Override
    public String handleRecharge(BigDecimal amount, String token) {
        /*
        下面直接获取userInfo没有校验,有可能存在没有绑定汇付宝账户的可能性
        //获取userId
        Long userId = JwtUtils.getUserId(token);
        //根据userId获取bindCode
        UserInfo userInfo = userInfoService.getById(userId); */
        //下面的这个方法里面会校验用户的hfb账户的绑定状态
        UserInfo userInfo = userInfoService.checkUserInfoByToken(token);
        //获取bindCode
        String bindCode = userInfo.getBindCode();

        //校验一下amount不能为0 compareTo 返回值有 -1(比目标小) 0(和目标相等) 1(比目标大)
        Asserts.AssertTrue(amount.compareTo(new BigDecimal(0)) > 0, ResponseEnum.BORROW_AMOUNT_NULL_ERROR);

        //准备参数
        Map<String,Object> params = new HashMap<>();
        params.put("agentId", HfbConst.AGENT_ID);//hfb给商户分配的唯一的标识
        String chargeNo = LendNoUtils.getChargeNo();
        params.put("agentBillNo",chargeNo);//商户充值的单号,要求要唯一
        params.put("bindCode",bindCode);//充值人绑定的协议号
        params.put("chargeAmt",amount);//充值的金额
        params.put("feeAmt",new BigDecimal(0));//需要收取的手续费,这里我们暂时不收取手续费
        params.put("notifyUrl",HfbConst.RECHARGE_NOTIFY_URL);//回调的接口地址
        params.put("returnUrl",HfbConst.RECHARGE_RETURN_URL);//充值完成后需要返回的页面的地址
        params.put("timestamp",System.currentTimeMillis());//时间戳
        //获取签名
        String sign = RequestHelper.getSign(params);
        params.put("sign",sign);//签名

        //保存交易的流水记录
        String memo = "账户充值,编号为:" + chargeNo;
        transFlowService.saveTransFlow(amount, userInfo, chargeNo,TransTypeEnum.RECHARGE,0,memo);

        //构建form表单
        return FormHelper.buildForm(HfbConst.RECHARGE_URL, params);
    }


    /**
     * 用户充值完成后,hfb异步通知的回调接口
     * @param params hfb异步通知的请求参数,里面包含一系列例如状态码等参数
     * @return success or fail 字符串
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public String callback(Map<String, Object> params) {
        //1校验参数
        //先从数据库中获取transFlow对象 只能通过交易单号获取到transFlow对象,把这个方法提取一下
        TransFlow transFlow = transFlowService.getByTransNo(params.get("agentBillNo").toString());
        //1.1校验返回来的信息的状态码是否成功也就是是否等于0001
        String resultCode = params.get("resultCode").toString();
        //这里不使用断言的方式来抛出异常,因为对于hfb来说,我们抛出自定义的异常,没有任何的意义,有问题返回fail字符串即可
        if(!params.containsKey("resultCode")||!StringUtils.equals(resultCode,"0001")){
            //如果结果集里面的resultCode不是0001或者,返回的结果集里面根本就没有resultCode的话,那么修改trans_flow的status状态,并且返回fail
            log.error("resultCode验证失败,回调的参数是:{}",JSON.toJSONString(params));
            transFlow.setStatus(3); //3代表resultCode异常
            transFlowService.updateById(transFlow);
            return "fail";
        }

        //1.2验签
        String sign = params.get("sign").toString();
        if(!RequestHelper.isSignEquals(params)){
            //进来这里面说明验签失败
            log.error("验签失败,回调的参数是:{}",JSON.toJSONString(params));
            transFlow.setStatus(4);//4 代表验签失败
            transFlowService.updateById(transFlow);
            return "fail";
        }

        //1.3验证充值金额是否相等
        BigDecimal chargeAmt = new BigDecimal(params.get("chargeAmt").toString());
        if(chargeAmt.compareTo(transFlow.getTransAmount()) != 0){
            //说明充值的金额不相等
            log.error("充值金额验证异常,回调的参数是:{}",JSON.toJSONString(params));
            transFlow.setStatus(5);//5 代表充值金额异常
            transFlowService.updateById(transFlow);
            return "fail";
        }

        //1.4确保幂等性,也就是确认transFlow的status是什么状态,如果是1(已完成) 那么直接返回,说明该笔交易已经处理完成了
        if(transFlow.getStatus() == 1){
            //进来这里面说明该笔充值的订单已经完成过了
            return "success";
        }

        //2.更新用户的account
        baseMapper.updateAmountByBindCode(params.get("bindCode").toString(),chargeAmt);
        //3.更新transFlow的memo(将hfb响应的参数保存进去)
        transFlow.setMemo(JSON.toJSONString(params));
        //4.更新transFlow的status为1(代表成功)
        transFlow.setStatus(1);
        transFlowService.updateById(transFlow);

        UserInfo userInfo = userInfoService.getOne(Wrappers.lambdaQuery(UserInfo.class)
                .eq(UserInfo::getBindCode, params.get("bindCode").toString()));

        //TODO 充值成功异步告诉rabbitMq需要sms模块发送短信给用户
        //创建一个map集合,用来存放消息的内容,该map集合会被使用base64的方式进行编码
        /* Map<String,Object> message = new HashMap<>();
        message.put("mobile",userInfo.getMobile());
        message.put("type",3);// 3 代表充值的短信模板
        message.put("amount",transFlow.getTransAmount());//amount代表用户所充值的金额
        rabbitTemplate.convertAndSend("srb.exchange","srb.sms.account",message); */
        log.info("发送短信消息,告诉用户充值成功");
        SmsDTO smsDTO = new SmsDTO();
        smsDTO.setType(3);
        smsDTO.setMobile(userInfo.getMobile());
        smsDTO.setMessage("充值成功,充值了"+transFlow.getTransAmount());
        // mqService.sendMessage(MQConst.EXCHANGE_TOPIC_SMS,MQConst.ROUTING_SMS_ITEM,smsDTO);
        rabbitTemplate.convertAndSend(MQConst.EXCHANGE_TOPIC_SMS,MQConst.ROUTING_SMS_ITEM,smsDTO);

        return "success";
    }

    @Override
    public BigDecimal getAmount(Long userId) {
        UserAccount userAccount = this.getOne(Wrappers.lambdaQuery(UserAccount.class)
                .eq(UserAccount::getUserId, userId).select(UserAccount::getAmount));
        //这个地方查询出来的userAccount有没有可能为空
        //校验一下,避免空指针,如果为null,那么就直接返回0,如果不为空的话再返回对应的数值
        return userAccount == null ? new BigDecimal(0) : userAccount.getAmount();
    }

    //计算预期的收益
    @Override
    public BigDecimal expectAmount(Integer period, BigDecimal yearRate, Integer returnMethod, BigDecimal amount) {
        BigDecimal expectAmount = null;
        switch (returnMethod){
            case 1:
                expectAmount = Amount1Helper.getInterestCount(amount,
                        yearRate.divide(new BigDecimal(100),8, RoundingMode.DOWN),period);
                break;
            case 2:
                expectAmount = Amount2Helper.getInterestCount(amount,
                        yearRate.divide(new BigDecimal(100),8, RoundingMode.DOWN),period);
                break;
            case 3:
                expectAmount = Amount3Helper.getInterestCount(amount,
                        yearRate.divide(new BigDecimal(100),8, RoundingMode.DOWN),period);
                break;
            case 4:
                expectAmount = Amount4Helper.getInterestCount(amount,
                        yearRate.divide(new BigDecimal(100),8, RoundingMode.DOWN),period);
                break;
        }
        return expectAmount;
    }

    @Override
    public Integer updateAmountAndFreezeAmountByUserId(BigDecimal amount, BigDecimal freezeAmount, Long userId, Date updateTime) {
        return baseMapper.updateAmountAndFreezeAmountByUserId(amount,freezeAmount,userId,updateTime);
    }

    @Override
    public Integer updateAmountAndFreezeAmountByBindCode(BigDecimal amount, BigDecimal freezeAmount, String bindCode, Date updateTime) {
        return baseMapper.updateAmountAndFreezeAmountByBindCode(amount,freezeAmount,bindCode,updateTime);
    }

    //提现的同步接口
    @Override
    public String withDraw(String token, BigDecimal amount) {
        //1.校验参数
        //1.1用户必须绑定了hfb的账户
        UserInfo userInfo = userInfoService.checkUserInfoByToken(token);
        //1.2用户账户必须是OK的,不能是锁定状态
        Asserts.AssertTrue(userInfo.getStatus() == 1,ResponseEnum.LOGIN_LOCKED_ERROR);
        //1.3用户提现的钱必须大于0并且小于等于账户余额
        //获取用户的余额
        BigDecimal balance = this.getAmount(userInfo.getId());
        Asserts.AssertTrue(balance.compareTo(amount) >= 0 && amount.compareTo(new BigDecimal(0)) > 0,ResponseEnum.WITHDRAW_FUNDS_ERROR);

        //2.构建参数,向hfb发起提现的请求
        Map<String,Object> params = new HashMap<>();
        params.put("agentId",HfbConst.AGENT_ID);//给商户分配的唯一标识
        String withdrawNo = LendNoUtils.getWithdrawNo();
        params.put("agentBillNo",withdrawNo);//商户提现单号
        params.put("bindCode",userInfo.getBindCode());//用户账户绑定码
        params.put("fetchAmt",amount);//提现金额。必须大于0。最多支持小数点后两位。
        params.put("feeAmt",new BigDecimal(0));//商户收取用户的手续费
        params.put("returnUrl",HfbConst.WITHDRAW_RETURN_URL);
        params.put("notifyUrl",HfbConst.WITHDRAW_NOTIFY_URL);
        params.put("timestamp",System.currentTimeMillis());
        params.put("sign",RequestHelper.getSign(params));

        //3.保存交易流水
       //提现的交易单号上面的参数里面有withdrawNo
        transFlowService.saveTransFlow(amount,userInfo,withdrawNo,TransTypeEnum.WITHDRAW,0,"");

        return FormHelper.buildForm(HfbConst.WITHDRAW_URL, params);

    }

    //提现的异步接口
    //{"agentBillNo":"WITHDRAW20230722082318688",
    // "bindCode":"355c3f04c99b4514a9f5fd41685a9658",
    // "fetchAmt":"500",
    // "mchFee":"0",
    // "resultCode":"0001",
    // "resultMsg":"充值成功",
    // "sign":"b3a93282a59f67d7d849af28ca2bc12c",
    // "timestamp":"1689985408376"}
    @Transactional(rollbackFor = Exception.class)
    @Override
    public String withDrawCallback(Map<String, Object> params) {
        //获取hfb的请求参数
        String agentBillNo = params.get("agentBillNo").toString();
        String bindCode = params.get("bindCode").toString();
        BigDecimal fetchAmt = new BigDecimal(params.get("fetchAmt").toString());
        String resultCode = params.get("resultCode").toString();
        String paramInfo = JSON.toJSONString(params);
        //1.参数校验
        TransFlow transFlow = transFlowService.getByTransNo(agentBillNo);
        //1.1校验幂等性
        //1.1.1transFlow为null
        if(transFlow == null){
            //说明没有经过我们的同步接口,而是直接向我们的异步接口发起的请求,属于非法的恶意攻击
            return "fail";
        }
        //1.1.2transFlow的状态是不是OK的
        if(transFlow.getStatus() != 0){
            //状态为1说明交易是成功的,其他的状态都是失败的,日志也不用打印了
            return transFlow.getStatus() == 1 ? "success" : "fail";
        }
        //1.2验签
        //根据交易编号获取transFlow对象
        //充值交易状态0 未完成; 1 已完成; 2 失败; 3 resultCode异常; 4 代表验签失败; 5 代表充值金额异常
        if(!RequestHelper.isSignEquals(params)){
            //如果验签失败,更新transfFlow状态
            transFlow.setStatus(4);
            //打印信息并更新memo备注
            log.error("提现失败,验签不通过,详细信息:{}",paramInfo);
            transFlow.setMemo(paramInfo);
            transFlowService.updateById(transFlow);
            //返回fail
            return "fail";
        }
        //1.3校验体现的金额不能≤0
        if(fetchAmt.compareTo(new BigDecimal(0)) <= 0){
            transFlow.setStatus(5);
            //打印信息并更新memo备注
            log.error("提现失败,金额错误,详细信息:{}",paramInfo);
            transFlow.setMemo(paramInfo);
            transFlowService.updateById(transFlow);
            //返回fail
            return "fail";
        }
        //1.4校验体现的金额是否匹配
        //同步方法里面接收到的提现的金额
        BigDecimal transAmount = transFlow.getTransAmount();
        //hfb异步通知里面接收到的提现的金额 fetchAmt
        if(transAmount.compareTo(fetchAmt) != 0){
            //进来这里面说明两个金额并不相等,所以有问题
            transFlow.setStatus(5);
            //打印信息并更新memo备注
            log.error("提现失败,体现金额不匹配,详细信息:{}",paramInfo);
            transFlow.setMemo(paramInfo);
            transFlowService.updateById(transFlow);
            //返回fail
            return "fail";
        }
        //1.5校验resultCode是否OK
        if(!params.containsKey("resultCode") || !StringUtils.equals("0001",resultCode)){
            transFlow.setStatus(3);
            //打印信息并更新memo备注
            log.error("提现失败,resultCode异常,详细信息:{}",paramInfo);
            transFlow.setMemo(paramInfo);
            transFlowService.updateById(transFlow);
            //返回fail
            return "fail";
        }
        //能走到这里说明都是OK的,接下来就要更新用户的账户的余额了
        this.updateAmountAndFreezeAmountByBindCode(transAmount.negate(),new BigDecimal(0),bindCode,new Date());

        //更新transFlow的status以及备注
        transFlow.setStatus(1);
        transFlow.setMemo(paramInfo);
        transFlowService.updateById(transFlow);
        return "success";
    }
    
}
